package CustomCMS
{
	import Undo.*;
	
	import UploadFiles.*;
	
	import flash.display.*;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.MouseEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.net.navigateToURL;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import mx.collections.ArrayCollection;
	import mx.collections.XMLListCollection;
	import mx.containers.Canvas;
	import mx.containers.Form;
	import mx.containers.FormItem;
	import mx.containers.HBox;
	import mx.containers.HDividedBox;
	import mx.containers.Panel;
	import mx.containers.VBox;
	import mx.controls.Alert;
	import mx.controls.Button;
	import mx.controls.ComboBox;
	import mx.controls.Image;
	import mx.controls.Label;
	import mx.controls.Text;
	import mx.controls.TextArea;
	import mx.controls.TextInput;
	import mx.controls.Tree;
	import mx.core.Application;
	import mx.core.Container;
	import mx.core.UIComponent;
	import mx.events.CloseEvent;
	import mx.events.FlexEvent;
	import mx.events.ListEvent;
	import mx.events.ListEventReason;
	import mx.utils.UIDUtil;
	/**
	* ...
	* @author DefaultUser (Tools -> Custom Arguments...)
	*/
	public class ClientApp extends Application
	{
		[Bindable] public var _selectedNode:XML;
		[Bindable] public var _treeCMS:Tree;
		[Bindable] public var _txtContent:TextArea;
		[Bindable] public var _txtLog:TextArea;
		[Bindable] public var _treeXml:XMLList;
		[Bindable] public var _treeTemplateXml:XMLList;
		[Bindable] public var _cnvPageForm:Canvas;
		[Bindable] public var _cnvForms:Canvas;
		[Bindable] public var _lblTitle:Label;
		[Bindable] public var _frmPage:Form;
		[Bindable] public var _pnlCreateDir:Panel;
		[Bindable] public var _divider:HDividedBox;
		[Bindable] public var _txtDirName:TextInput;
		[Bindable] public var _templateToolbar:HBox;
		[Bindable] public var _pageToolbar:HBox;
		[Bindable] public var _imageToolbar:HBox;
		[Bindable] public var _templateDirToolbar:HBox;
		[Bindable] public var _pageDirToolbar:HBox;
		[Bindable] public var _generalToolbar:HBox;
		[Bindable] public var _packagingToolbarButton:Button;
		[Bindable] public var _lockToolbar:HBox;
		[Bindable] public var _lockBtn:Button;
		[Bindable] public var _unlockBtn:Button;
		[Bindable] public var _lockLabel:Label;
		[Bindable] public var _currentItem:Label;
		[Bindable] public var _toolbars:HBox;
		[Bindable] public var _pnlCreateTemplate:Panel;
		[Bindable] public var _pnlCreatePage:Panel;
		[Bindable] public var _pnlChooseTemplate:Panel;
		[Bindable] public var _pnlUploadFiles:Panel;
		[Bindable] public var _txtTemplateName:TextInput
		[Bindable] public var _txtTemplateTitle:TextInput
		[Bindable] public var _txtTemplateComment:TextInput
		[Bindable] public var _txtPageName:TextInput
		[Bindable] public var _txtPageTitle:TextInput
		[Bindable] public var _txtPageComment:TextInput
		[Bindable] public var _treeTemplate:Tree;
		[Bindable] public var _treePage:Tree;
		[Bindable] public var _txtStatus:Text;
		[Bindable] public var _fuUploader:ApplicationClass;
		[Bindable] public var _txtUsername:TextInput;
		[Bindable] public var _txtPassword:TextInput;
		[Bindable] public var _versions:ComboBox;
		[Bindable] public var _pnlPackagePortal:Panel;
		[Bindable] public var _packagingForm:Form;
		
		[Embed(source="../images/folder_unlocked.png")]	public var icon_folder_unlocked:Class;
		[Embed(source="../images/folder_locked.png")]	public var icon_folder_locked:Class;
		[Embed(source="../images/script_unlocked.png")]	public var icon_template_unlocked:Class;
		[Embed(source="../images/script_locked.png")]	public var icon_template_locked:Class;
		[Embed(source="../images/page_unlocked.png")]	public var icon_page_unlocked:Class;
		[Embed(source="../images/page_locked.png")]		public var icon_page_locked:Class;
		[Embed(source="../images/photo_unlocked.png")]	public var icon_image_unlocked:Class;
		[Embed(source="../images/photo_locked.png")]	public var icon_image_locked:Class;
		
		private var _conn:String = "";
		private var _repo:String = "";
		private var _username:String = null;
		private var _treeResult:String = "";
		public var _uploadPage:String = "";
		private var _isDirty:Boolean = false;
		private var _editCell:int = -1;
		private var _editTime:int = 0;
		private var _undoStack:UndoStack;
		private var _displayedNode:Object;
		private var _statusTimer:Timer;
		private var _packaging:XMLList;
		
		public function ClientApp() 
		{
			super();
		}
		
		override public function initialize() : void
		{
			super.initialize();
			
			//production
			_conn = Application.application.parameters.conn;
			_repo = Application.application.parameters.repo;
			
			if (!_conn || !_repo)
			{
				//dev
				_conn = "http://localhost:3067/interface/Default.aspx";			
				_repo = "http://localhost/repo/";
				//_conn = "http://local/customcms/Interface/Default.aspx";			
				//_repo = "http://local/customcms/repo/";
			}
									
			_lblTitle.text = "version " + Util.Version;
			ShowRelevantToolbar(null);
			
			_treeCMS.enabled = false;
			_cnvForms.enabled = false;
			_divider.enabled = false;
			
			_undoStack = new UndoStack(100);
		}

		public function GetUploadPage():String
		{
			var attr:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			var server:String = _conn.toLowerCase().replace("default.aspx","upload.aspx");
			
			return server + "?path=" + attr.GetFullPath() + "*&userId=" + _username;
		}

		public function LoadTreeEvent(evt:Event):void
		{
			ShowResult(evt);
			ShowRelevantToolbar(null);
			ShowForm(Forms.WELCOME);
			LoadTree();
		}
		
		public function ShowResult(evt:Event) : void
		{
			var result:Object = GetResult(evt);
			if (result.isSuccess) _txtStatus.text = result.text;
			else alert(result.text);
		}
		
		private function ShowSaveResult(event:Event) : void
		{
			ShowResult(event);
			if (GetResult(event).isSuccess) SetDirty(false);
		}
		
		private function GetResult(event:Event) : Object
		{
			var response:String = event.target.data as String;
			var result:XMLList = new XMLList(response);
			var isSuccess:Boolean = result.attribute("status").toLowerCase() == "success";
			return {isSuccess:isSuccess, text: isSuccess? result.text() : result.child(0)};
		}
		
		public function ShowResultAndRefresh(evt:Event) : void
		{
			ShowResult(evt);
			var response:String = evt.target.data as String;
			var result:XMLList = new XMLList(response);
			if (result.attribute("status").toLowerCase() == "success") LoadTree();
		}

		public function TestPage() : void
		{
			var id:String = _treeCMS.selectedItem.@id;
			var parent:CMSAttributes = new CMSAttributes((_treeCMS.selectedItem as XML).parent().@id);
			
			var path:String = parent.GetFullPath().replace(/\*/g, "/");
			
			var url:URLRequest = new URLRequest((_repo + path + id).replace(/\/\//g, "/"));
			navigateToURL(url, "customcms_testing");
		}
		
		public function LockItem() : void
		{
			SetLock(_treeCMS.selectedItem as XML, true);
		}
		
		public function UnlockItem() : void
		{
			SetLock(_treeCMS.selectedItem as XML, false);
		}
		
		private function SetLock(node:XML, locked:Boolean) : void
		{
			var attributes:Object = ItemAttributes(node);
			
			var vars:URLVariables = new URLVariables();
			vars.action = (locked? "lock_" : "unlock_") + (attributes.isTemplate? "template" : "page");
			vars.path = attributes.path + "*";
			vars.elementId = attributes.id;
			vars.type = attributes.type;
			vars.userId = _username;
							
			WebRequest.Post(_conn, ShowResultAndRefresh, vars);
		}
		
		private function ItemAttributes(node:XML) : Object
		{
			if (node == null) return null;
			var id:String = node.@id;
			var type:String = node.@type;
			var locker:String = node.@lockedBy;
			var isLocked:Boolean = locker && locker.length;
			var isTemplate:Boolean = false;
			var isDir:Boolean = type == "dir";
			var path:String;
			var parent:XML = node.parent();
			var isRoot:Boolean = parent == null;
			var parentId:String = isRoot? null : parent.@id;
			if (!isRoot && parent.@type == "dir") path = parentId.replace(/^root\*((template|page)\*?)?/, "");
			if (type == "file") type = (parentId && /^root\*template/.test(parentId))? "template" : "page";
			else if (isDir && id.indexOf("root*template") > 0 ) isTemplate = true;
			return {id:id, parentId:parentId, path:path, type:type, isRoot:isRoot, isPage:type=="page", isTemplate:isTemplate||type=="template", isImage:type=="image", isDir:isDir, isLocked:isLocked, locker:locker};
		}

		public function LoadTree(callback:Function = null) : void
		{
			_editCell = -1;
			_treeCMS.data = MarshalState(_treeCMS);

			_treeXml = new XMLList();
			
			var dict:Array = new Array();
			dict.push({name:"action", val:"get_tree"});
			dict.push({name:"userId", val:_username});

			WebRequest.Get(_conn, callback || HandleTree, dict);
		}

		public function applicationCompleted():void
		{
			_txtUsername.setFocus();
		}
		
		private function HandleTree(evt:Event):void
		{
			var response:String = evt.target.data as String;
		 	if (!IsResponseSuccessful(response)) return;

			try { _treeXml = new XMLList(response);	} 
			catch(e:TypeError) { trace("the response data was not in valid XML format"); }								

			UnmarshalState(_treeCMS, _treeCMS.data as Object);
			_selectedNode = _treeCMS.selectedItem as XML;
			ShowRelevantToolbar(_selectedNode);
		}
		
		private function MarshalState(tree:Tree) : Object
		{
			return {selected:MarshalItem(tree.selectedItem as XML), open:MarshalItems(tree.openItems as Array)};
		}
		
		private function MarshalItems(list:Array) : Array
		{
			if (!list || !list.length) return null;
			var items:Array = new Array();
			for (var i:int = 0; i != list.length; ++i) items.push(MarshalItem(list[i] as XML));
			return items;
		}
		
		private function MarshalItem(node:XML) : String
		{
			if (!node) return null;
			var id:String = node.@id;
			var x:int = id.indexOf("*");
			if (id.indexOf("*") == -1) 
			{
				var parent:XML = node.parent() as XML;
				id = (parent? parent.@id + "*" : "") + id;
			}
			return id;
		}
		
		private function UnmarshalItems(xml:XMLList, items:Array) : Array
		{
			if (!items) return null;
			var nodes:Array = [];
			for (var i:int = 0; i != items.length; ++i)
			{
				var node:XML = UnmarshalItem(xml, items[i] as String);
				if (node) nodes.push(node);
			}
			return nodes;
		}

		private function UnmarshalItem(xml:XMLList, id:String) : XML
		{
			if (id == null) return null;
			var node:XML = xml..node.(@id == id)[0];
			if (!node) 
			{
				var x:int = id.lastIndexOf("*");
				if (x >= 0) 
				{
					var parentId:String = id.substr(0, x);
					id = id.substr(x+1);
					node = xml..node.(@id == parentId).node.(@id == id)[0];
				}
			}
			return node;
		}
			
		private function UnmarshalState(tree:Tree, state:Object) : void
		{
			tree.validateNow();
			
			var data:XMLList = (tree.dataProvider as XMLListCollection).source;
			tree.openItems = UnmarshalItems(data, state.open as Array);
			
			var selected:XML = UnmarshalItem(data, state.selected as String);
			ExpandAndSelect(tree, selected);
			
			//tree.selectedItem = selected;
//			tree.selectedIndex = CalcIndex(selected);
		}
		
		private function ExpandAndSelect(tree:Tree, selected:XML) : void
		{
			if (!selected) return;
			var node:XML = selected;
			while (node = node.parent() as XML) tree.expandItem(node, true);
			Select(tree, selected);
		}
		
		private function Select(tree:Tree, selected:XML) : void
		{
			tree.validateProperties();
			tree.validateDisplayList();
			var pos:int = CountVisible(tree, selected, false);
			tree.selectedIndex = pos;
		}
		
		private function CountVisible(tree:Tree, node:XML, includingNode:Boolean) : int
		{
			var parent:XML = node.parent() as XML;
			if (!parent) return -1;
			var pos:int = 0;
			var nodes:XMLList = parent.descendants();
			for (var i:int = 0; i != nodes.length(); ++i) 
				if (nodes[i] == node) return pos + CountVisible(tree, parent, false) + 1;
				else if (tree.isItemVisible(nodes[i])) pos++;
			return 0;
		}
		
		private function CalcIndex(node:XML) : int
		{
			if (!node) return -1;
			var n:int = node.childIndex();
			var pos:int = n;
			var parent:XML = node.parent();
			if (parent) for (var i:int = 0; i < n; ++i) pos += XML(parent.children()[i]).descendants().length();
			return CalcIndex(parent as XML) + pos;
		}
		
		private function CalcNodeAtIndex(xml:XMLList, pos:int) : XML
		{
			var x:XMLList = xml.descendants();
			var n:XML = x[pos] as XML;
			return xml.descendants()[pos] as XML;
		}
		
		public function GetTreeNodeIcon(item:Object):Class
		{
			var node:XML = XML(item);
			var attr:Object = ItemAttributes(node);

			if (attr.isDir) return attr.isLocked? icon_folder_locked : icon_folder_unlocked;
			else if (attr.isImage) return attr.isLocked? icon_image_locked : icon_image_unlocked;
			else if (attr.isTemplate) return attr.isLocked? icon_template_locked : icon_template_unlocked;
			else return attr.isLocked? icon_page_locked : icon_page_unlocked;
		}
		
		public function GetTreeNodeLabel(item:Object):String
		{
			var node:XML = XML(item);
			var id:String = node.@id;
			if ( id == "_no_items_found_")
			{
				node.@clickable = "false";
				return "no items...";
			}
			
			var pos:int = id.lastIndexOf("*"); 
			return pos == -1? id : id.substr(pos + 1);
		}

		private function ShowRelevantToolbar(node:XML):void
		{
			_toolbars.removeAllChildren();
			if (!_username) return;
			
			_toolbars.addChild(_generalToolbar);
			_packagingToolbarButton.alpha = (_packagingToolbarButton.tabEnabled = _packagingToolbarButton.mouseEnabled = _packaging.length() > 0)? 1 : 0.5;
			
			if (!node) return;
			
			var isImage:Boolean = node.@type == "image";
			var isFile:Boolean = node.@type == "file" || isImage;
			var locker:String = node.@lockedBy;
			var isLocked:Boolean = locker && locker.length;
			
			if(isFile) node = node.parent();

			var attr:CMSAttributes = new CMSAttributes(node.@id);
						
			if( attr.GetType() == "template" && !isFile)
				_toolbars.addChild(_templateDirToolbar);
			else if( attr.GetType() == "template" && isFile)
			{
				_toolbars.addChild(_templateToolbar);
				_toolbars.addChild(_lockToolbar);
			}
			else if( attr.GetType() == "page" && !isFile)
				_toolbars.addChild(_pageDirToolbar);
			else if( attr.GetType() == "page" && isFile && !isImage)
			{
				_toolbars.addChild(_pageToolbar);
				_toolbars.addChild(_lockToolbar);
			}
			else if(isImage)
				_toolbars.addChild(_imageToolbar);
			
			show(_lockBtn, !isLocked);
			show(_unlockBtn, isLocked);
			show(_lockLabel, isLocked);
			if (isLocked) _lockLabel.text = "(locked by " + locker + ")";
			
			var isEnabled:Boolean = !(isLocked && locker != _username);
			_templateToolbar.enabled = isEnabled;
			_templateDirToolbar.enabled = isEnabled;
			_lockToolbar.enabled = isEnabled;
			_pageDirToolbar.enabled = isEnabled;
			_pageToolbar.enabled = isEnabled;
			_imageToolbar.enabled = isEnabled;
			
			_cnvForms.enabled = isEnabled;
		}
		
		private function show(component:UIComponent, shown:Boolean) : void
		{
			component.visible = shown;
			component.includeInLayout = shown;
		}

		public function SaveChanges(event:CloseEvent) : void 
		{
			var selected:XML = (event.target as Alert).data as XML;
			if (event.detail == Alert.NO) 
			{
				SetDirty(false);
				ExpandAndSelect(_treeCMS, selected);
				treeChanged(null);
				return;
			}
			ExpandAndSelect(_treeCMS, _selectedNode);
			if (event.detail == Alert.CANCEL) return;
			var attr:Object = ItemAttributes(_selectedNode);
			if (attr.type == "page") SavePage(SwitchToSelectedItem);
			else if (attr.type == "template") SaveTemplate(SwitchToSelectedItem);
			_selectedNode = selected;
		}
		
		private function SwitchToSelectedItem(event:Event) : void
		{
			ShowSaveResult(event);
			if (GetResult(event).isSuccess) Select(_treeCMS, _selectedNode);
			treeChanged(null);
		}
		
		// Event handler for the Tree control change event.
		public function treeChanged(event:Event):void 
		{
			_editCell = -1;
			
			if (HandleDirty(SaveChanges, _treeCMS.selectedItem as XML)) return;
			
			_txtStatus.text = "";
			
			_selectedNode = _treeCMS.selectedItem as XML;
			var attributes:Object = ItemAttributes(_selectedNode);

			_versions.dataProvider = null;
			ShowRelevantToolbar(_selectedNode);

			if (attributes.isRoot || attributes.isDir || attributes.id == "_no_items_found_")
			{
				ShowForm(Forms.WELCOME);
				return;
			}
			 						
			var dict:Array = new Array();
			
			if (attributes.isTemplate)
			{
				dict.push({name:"action", val:"get_template"});
				dict.push({name:"userId", val:_username});
				dict.push({name:"templateId", val:attributes.id});
				dict.push({name:"path", val:attributes.path+"*"});
				WebRequest.Get(_conn, ShowTemplate, dict);
			}
			else if (attributes.isPage)
			{
				dict.push({name:"action", val:"get_page"});
				dict.push({name:"userId", val:_username});
				dict.push({name:"pageId", val:attributes.id});
				dict.push({name:"path", val:attributes.path+"*"});
				WebRequest.Get(_conn, ShowPage, dict);
			}
			else if (attributes.isImage) ShowPageForm();
		}
		
		public function ShowTemplate(evt:Event):void
		{
			SetDirty(false);
			ShowForm(Forms.TEMPLATE);
			
			var response:String = evt.target.data as String;
						
			if (!IsResponseSuccessful(response))
			{
				Alert.show("Failed to retrieve template:\n"+GetFailReason(response));
				_txtContent.text = "";
				return;
			}

			var xml:XML = new XML(response);
//			var start:int = response.indexOf("<result status=\"Success\">")+ "<result status=\"Success\">".length;
//			var end:int = response.lastIndexOf("</result>");
			
//			_txtContent.text = response.substring(start,end);
			_txtContent.text = xml.root[0][0].toString();
			
			_undoStack.clear();
			_undoStack.listenTo(_txtContent);

			_displayedNode = UpdateVersions(xml.versions[0], "template");
		}
		
		private function IsResponseSuccessful(response:String) : Boolean
		{
			return (/<result\b[^>]+?status="Success"/.test(response));			
		}
		
		private function GetFailReason(response:String) : String
		{
			return IsResponseSuccessful(response)? null : /<result\b[^>]+>(.*?)<\/result>/s.exec(response)[1];
		}
		
		private function ShowPageForm() : void
		{
			SetDirty(false);
			ShowForm(Forms.PAGE);
			_frmPage.removeAllChildren();
		}

		public function ShowPage(evt:Event) : void
		{
			ShowPageForm();

			var response:String = evt.target.data as String;
			if (!IsResponseSuccessful(response))
			{
				Alert.show("Failed to retrieve page:\n"+GetFailReason(response));
				return;
			}
			
			XML.ignoreWhitespace = false;
			var xml:XML = new XML(response);
			
			var missing:XMLList = xml.missing.template;
			if (missing.length())
			{
				for (var m:Array = [], i:int = 0; i != missing.length(); ++i) m.push(missing[i].toString());
				var warning:Text = new Text();
				warning.text = "This page is missing the following template" + (missing.length() == 1? "" : "s") + ": " + m.join(" ; ");
				warning.styleName = "warning";
				
				var item:FormItem = new FormItem();
				item.addChild(warning);
				
				_frmPage.addChild(item);
			}
			
			var state:XMLList = xml.state.section;
			
			_undoStack.clear();
			
			var template:XMLList = xml.root.template as XMLList;
			var templateName:String = template.text().toString();
			
			if (templateName) AddFormTextWithButton(_frmPage, "templateField", "Master template", beautifyTemplatePath(templateName), true, "Change...", ShowChooseTemplateForm);

			//AddTreeInput(_frmPage, "Master template", templateName, true, PopulateTemplateTree());

			var section:String = "", formSection:Container;
			var plist:XMLList = xml.root.param;
			
			for (var count:int = 0; count < plist.length(); count++)
			{
				var child:XML = plist[count];

				if(child.attribute("section") != section)
				{
					if (formSection) _frmPage.addChild(formSection);
					section = child.attribute("section");
					formSection = AddFormSection(_frmPage, section, state.(@name==section).@state);
				}	
								
				var values:String = child.attribute("values");
				if(values.length > 0)
					AddFormDropDown(formSection, child.attribute("name"), values, child.text(), true, child.attribute("section"));
				else
					AddFormTextInput(formSection, child.attribute("name"), child.text(), true, true, child.attribute("section"));
			}
			if (formSection) _frmPage.addChild(formSection);
			
			_displayedNode = UpdateVersions(xml.versions[0], "page");
		}
		
		private function ShowForm(form:String):void
		{
			this.currentState = form;
		}
		
		private function AddFormSection(form:Form, section:String, state:int) : Container
		{
			var item:VBox = new VBox();
			item.percentWidth = 100;
			item.styleName = "section";

			var hb:HBox = new HBox();
			hb.styleName = "sectionTitle";
			hb.percentWidth = 100;
			
			if (state != 0)
			{
				var c:Image = new Image();
				c.width = c.height = 8;
				hb.addChild(c);
				hb.addEventListener(MouseEvent.CLICK, ToggleCollapseExpand);
				hb.addEventListener(MouseEvent.MOUSE_OVER, function(event:MouseEvent):void { (event.currentTarget as HBox).setStyle("backgroundColor", "#7FCEFF"); });
				hb.addEventListener(MouseEvent.MOUSE_OUT, function(event:MouseEvent):void { (event.currentTarget as HBox).setStyle("backgroundColor", null); });
				item.data = c;
				callLater(SetState, [c, state != 1]);
			}
			else hb.setStyle("paddingLeft", 16);
			
			var text:Text = new Text();
			text.text = section;
			text.width = 200;
			hb.addChild(text);
			item.addChild(hb);
			return item;
		}
		
		private function ToggleCollapseExpand(event:MouseEvent) : void
		{
			var c:Image = (event.currentTarget as HBox).getChildAt(0) as Image;
			SetState(c, !(c.data as Boolean));
			for (var o:Object = c.parent; o && o.constructor != Form; o = (o as DisplayObjectContainer).parent);
			if (o) SaveSectionStates(o as Form);
		}
		
		private function SetState(c:Image, expanded:Boolean) : void
		{
			c.data = expanded;
			c.source = '../images/'+(expanded? "collapse" : "expand")+'.png';
			c.parent.parent.height = expanded? NaN : c.parent.height + 5;
		}
		
		private function AddFormDropDown(form:Container,label:String,validVals:String,val:String,isEnabled:Boolean,section:String) : void
		{
			var item:FormItem = new FormItem();
			item.percentWidth = 100;
			item.styleName = "sectionItem";
			item.label = label;
			
			var combo:ComboBox = new ComboBox();
			
			var vals:Array = validVals.split("|");

			combo.dataProvider = vals;
			
			combo.selectedItem = val;
			
			//combo.percentWidth = 100;
			combo.enabled = isEnabled;
			combo.addEventListener(Event.CHANGE, Dirty);
			
			_undoStack.listenTo(combo);

			item.addChild(combo);
			item.data = section;
			form.addChild(item);
		}
		
		public function Dirty(event:Event) : void
		{
			SetDirty(true);
		}
		
		private function SetDirty(isDirty:Boolean) : void
		{
			_isDirty = isDirty;
		}
		
		private function AddFormTextWithButton(form:Form, name:String, label:String, val:String, isEnabled:Boolean, btnLabel:String, callback:Function) : void
		{
			var item:FormItem = new FormItem();
			item.name = name;
			item.label = label;
			item.direction = "horizontal";
			
			var text:TextInput = new TextInput();
			text.text = val;
			text.enabled = false;
			text.width = 300;
			text.addEventListener(FlexEvent.VALUE_COMMIT, SaveAndReload);
			item.addChild(text);
			
			var btn:Button = new Button();
			btn.label = btnLabel;
			btn.addEventListener(MouseEvent.CLICK,callback);
			btn.data = text;
			item.addChild(btn);
			
			form.addChild(item);
		}
		
		private function SaveAndReload(event:Event) : void
		{
			SavePage(ReloadTree);
		}
		
		private function ReloadTree(event:Event) : void
		{
			ShowSaveResult(event);
			if (GetResult(event).isSuccess) LoadTree(ReloadPage);
		}
		
		private function ReloadPage(event:Event) : void
		{
			HandleTree(event);
			treeChanged(null);
		}
		
		private function AddFormTextInput(form:Container,label:String,val:String,isArea:Boolean,isEnabled:Boolean,section:String) : void
		{
			var item:FormItem = new FormItem();
			item.percentWidth = 100;
			item.styleName = "sectionItem";
			item.label = label;
			
			var comp:UIComponent;
			
			if(isArea)
			{
				comp = new ExpandingTextEditor();
				(comp as ExpandingTextEditor).text = val;
				(comp as ExpandingTextEditor).addEventListener(Event.CHANGE, Dirty);
				_undoStack.listenTo(comp as ExpandingTextEditor);
			}
			else
			{
				comp = new TextInput();
				(comp as TextInput).text = val;
				(comp as TextInput).addEventListener(Event.CHANGE, Dirty);
			}
			
			comp.percentWidth = 100;
			comp.enabled = isEnabled;
			item.addChild(comp);

			item.data = section;
			
			form.addChild(item);
		}
		
 		private function AddTreeInput(form:Form,label:String,val:String,isEnabled:Boolean,list:XMLList) : void
		{
			var tree:Tree = new Tree();
			tree.dataProvider = list;
			tree.showRoot = false;
			tree.width = 600;
//			tree.height = 24;
			
			tree.enabled = isEnabled;
			tree.labelFunction = GetTreeNodeLabel;
			tree.data = val;
			tree.addEventListener(FlexEvent.CREATION_COMPLETE, ExpandTree);
//			tree.addEventListener(flash.events.FocusEvent.FOCUS_IN, EnlargeTree);
//			tree.addEventListener(flash.events.FocusEvent.FOCUS_OUT, ContractTree);
			
			var item:FormItem = new FormItem();
			item.label = label;
			item.addChild(tree);
			
			form.addChild(item);
		}
		
		private function EnlargeTree(event:flash.events.FocusEvent):void 
		{
			var tree:Tree = event.target as Tree;
			tree.height = 100;
		}
		
		private function ContractTree(event:flash.events.FocusEvent):void 
		{
			var tree:Tree = event.target as Tree;
			tree.height = 24;
			tree.firstVisibleItem = XML(tree.selectedItem);
		}
		
		private function ExpandTree(event:Event):void 
		{
			var tree:Tree = event.target as Tree;
//			tree.removeEventListener(FlexEvent.CREATION_COMPLETE, ExpandTree);
			var list:XMLList = (tree.dataProvider as XMLListCollection).source;

			var val:String = tree.data as String;
			val = val.replace(/\//g, "*").replace(/^root\*template\*|^/, "root*template*");
			var i:int = val.lastIndexOf("*");
			var parent:String = val.substr(0, i);
			var id:String = val.substr(i+1);
			var selected:XML = list..node.(@id == parent).node.(@id == id)[0] as XML;
			if (!selected) selected = list.(@id == parent).node.(@id == id)[0] as XML;
			if (!selected) return;
			
			ExpandAndSelect(tree, selected);
			//tree.firstVisibleItem = tree.selectedItem;
        }

		private function alert(text:String):void
		{
			Alert.show(text);
		}

		public function ShowNewDirForm():void
		{
			_selectedNode = _treeCMS.selectedItem as XML;
			
			if ( _selectedNode == null || _selectedNode.@type != "dir" )
			{
				Alert.show("Please select a parent folder");
				return;	
			}

			ShowPopup(_pnlCreateDir,true);
		}

		public function ShowNewPageForm():void
		{
			_selectedNode = _treeCMS.selectedItem as XML;
			
			if ( _selectedNode == null || _selectedNode.@type != "dir" )
			{
				Alert.show("Please select a page parent folder");
				return;	
			}

			var dirAttr:CMSAttributes = new CMSAttributes(_selectedNode.@id);

			if(dirAttr.GetType() != "page")
			{
				Alert.show("Please select a page parent folder");
				return;	
			}
			
			PopulateTemplateTree();
			
			ShowPopup(_pnlCreatePage,true);
		}
		
		public function ShowChooseTemplateForm(event:MouseEvent):void
		{
			var text:TextInput = event.target.data as TextInput;
			_pnlChooseTemplate.data = text;
			
			_treeTemplate.dataProvider = PopulateTemplateTree();
			_treeTemplate.data = text.text;
			_treeTemplate.addEventListener(FlexEvent.CREATION_COMPLETE, ExpandTree);
			_treeTemplate.addEventListener(FlexEvent.UPDATE_COMPLETE, ExpandTree);
			//ExpandAndSelect(_treeTemplate, text.text);

			ShowPopup(_pnlChooseTemplate,true);
		}
		
		private function PopulateTemplateTree():XMLList
		{
			for(var i:int = 0; i < _treeXml.length(); i++)
			{
				if(new CMSAttributes(_treeXml.child(i).@id).GetType() == "template")
				{
					_treeTemplateXml = _treeXml.child(0).copy();
					break;
				}	
			}
			return _treeTemplateXml;
		}
		
		public function ShowUploadForm():void
		{
			_selectedNode = _treeCMS.selectedItem as XML;
						
			if ( _selectedNode == null || _selectedNode.@type != "dir" )
			{
				Alert.show("Please select a page parent folder");
				return;	
			}

			var dirAttr:CMSAttributes = new CMSAttributes(_selectedNode.@id);

			if(dirAttr.GetType() != "page")
			{
				Alert.show("Please select a page parent folder");
				return;	
			}
						
			ShowPopup(_pnlUploadFiles,true);
		}
		
		public function CancelUpload():void
		{
			_fuUploader.cancelButton.dispatchEvent(new flash.events.MouseEvent(MouseEvent.CLICK));
			_fuUploader.clearButton.dispatchEvent(new flash.events.MouseEvent(MouseEvent.CLICK));
			HidePopups();
			LoadTree();
		}
		
		public function ShowNewTemplateForm():void
		{
			_selectedNode = _treeCMS.selectedItem as XML;
			
			if ( _selectedNode == null || _selectedNode.@type != "dir" )
			{
				Alert.show("Please select a template parent folder");
				return;	
			}

			var dirAttr:CMSAttributes = new CMSAttributes(_selectedNode.@id);

			if(dirAttr.GetType() != "template")
			{
				Alert.show("Please select a template parent folder");
				return;	
			}

			ShowPopup(_pnlCreateTemplate,true);
		}

		public function CreateNewDir():void
		{
			if(!Util.ValidFileName(_txtDirName.text))
			{
				Alert.show("Invalid directory name");
				return;
			}
			
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"create_directory"});
			dict.push({name:"userId", val:_username});
			dict.push({name:"type", val:parent.GetType()});
			dict.push({name:"path", val:parent.GetFullPath() + "*" + _txtDirName.text});
			
			WebRequest.Get(_conn, LoadTreeEvent,dict);
			
			HidePopups();
		}

		public function CreateNewTemplate():void
		{
			if(!Util.ValidFileName(_txtTemplateName.text))
			{
				Alert.show("Invalid template name");
				return;
			}
			
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"set_template"});
			dict.push({name:"path", val:parent.GetFullPath() + "*"});
			dict.push({name:"templateId", val:_txtTemplateName.text});
			dict.push({name:"userId", val:_username});
			dict.push({name:"content", val:""});
			dict.push({name:"comment", val:_txtTemplateComment.text});
			dict.push({name:"title", val:_txtTemplateTitle.text});

			WebRequest.Get(_conn, LoadTreeEvent,dict);
			
			HidePopups();
		}

		public function ChooseTemplate():void
		{
			var node:XML = _treeTemplate.selectedItem as XML;
			if (node == null || node.@type != "file")
			{
				alert("Please select a template");
				return;
			}

			var parent:XML = node.parent();
			var template:String = beautifyTemplatePath((parent? parent.@id + "*" : "") + node.@id);

			var text:TextInput = _pnlChooseTemplate.data as TextInput;
			if (text.text != template) text.text = template;
			
			HidePopups();
		}
		
		private function beautifyTemplatePath(path:String) : String
		{
			return path? path.replace(/\*/g,"/").replace(/^root\/template\/?|^\//,"") : path;
		}
		
		public function CreateNewPage():void
		{
			if(!Util.ValidFileName(_txtPageName.text))
			{
				Alert.show("Invalid page name");
				return;
			}
			
			if(_treePage.selectedItem == null || _treePage.selectedItem.@type != "file")
			{
				Alert.show("Please select a template");
				return;
			}
						
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			var template:CMSAttributes = new CMSAttributes(_treePage.selectedItem.@id);
			var templateParent:CMSAttributes = new CMSAttributes(_treePage.selectedItem.parent().@id);
			
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"set_page"});
			dict.push({name:"path", val:parent.GetFullPath() + "*"});
			dict.push({name:"templateId", val:templateParent.GetFullPath() + "*" + template.GetName()});
			dict.push({name:"pageId", val:_txtPageName.text});
			dict.push({name:"userId", val:_username});
			dict.push({name:"content", val:"<root></root>"});
			dict.push({name:"comment", val:_txtPageComment.text});
			dict.push({name:"title", val:_txtPageTitle.text});
			dict.push({name:"states", val:"<states/>"});

			WebRequest.Get(_conn, LoadTreeEvent,dict);
			
			HidePopups();
		}
		
		public function HidePopups():void
		{
			ShowPopup(_pnlCreateDir,false);
			ShowPopup(_pnlCreateTemplate,false);
			ShowPopup(_pnlChooseTemplate,false);
			ShowPopup(_pnlCreatePage,false);
			ShowPopup(_pnlUploadFiles,false);
			ShowPopup(_pnlPackagePortal,false);
			_toolbars.enabled = true;
		}
		
		private function ShowPopup(panel:Panel, show:Boolean) : void
		{
			//cleanup
			_txtTemplateComment.text = "";
			_txtTemplateName.text = "";
			_txtTemplateTitle.text = "";
			_txtPageComment.text = "";
			_txtPageName.text = "";
			_txtPageTitle.text = "";
			_txtDirName.text = "";
//			_txtPackageLanguage.text = "";

			//popping up
			panel.visible = show;
			_treeCMS.enabled = !show;
			_cnvForms.enabled = !show;
			_divider.enabled = !show;
			_toolbars.enabled = !show;
		}

		public function AskDeleteImage():void
		{
			Alert.show("Are you sure you wish to delete this image?", "Delete Image", Alert.OK | Alert.CANCEL, null, DeletePage, null, Alert.CANCEL);
		}
				
		public function AskDeleteDir():void 
		{
			_selectedNode = _treeCMS.selectedItem as XML;
			
			if( _selectedNode == null)
			{
				Alert.show("no directory selected");
				return;
			}
			 
			Alert.show("Are you sure you wish to delete this directory?", "Delete directory", Alert.OK | Alert.CANCEL, null, DeleteDir, null, Alert.CANCEL);
		}
		public function DeleteDir(evt:Object):void
		{
			if (evt.detail != Alert.OK)
				return;

			_selectedNode = _treeCMS.selectedItem as XML;

			var attr:CMSAttributes = new CMSAttributes(_selectedNode.@id);

			var dict:Array = new Array();
							
			dict.push({name:"action", val:"delete_"+attr.GetType()+"_directory"});
			dict.push({name:"path", val:attr.GetFullPath() + "*"});
			dict.push({name:"type", val:attr.GetType()});
			dict.push({name:"userId", val:_username});
			
			WebRequest.Get(_conn, LoadTreeEvent,dict);

		}
		
		public function AskDeletePage():void { Alert.show("Are you sure you wish to delete this page?", "Delete page", Alert.OK | Alert.CANCEL, null, DeletePage, null, Alert.CANCEL);}		
		public function DeletePage(evt:Object):void
		{
			if (evt.detail != Alert.OK)
				return;

			_selectedNode = _treeCMS.selectedItem as XML;

			var attr:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.parent().@id);

			var dict:Array = new Array();
							
			dict.push({name:"action", val:"delete_page"});
			dict.push({name:"path", val:parent.GetFullPath() + "*"});
			dict.push({name:"elementId", val:attr.GetName()});
			dict.push({name:"type", val:parent.GetType()});
			dict.push({name:"userId", val:_username});
						
			WebRequest.Get(_conn, LoadTreeEvent,dict);
		}

		public function treeEditBeginning(event:ListEvent) : void
		{
			if (_editCell == -1 || getTimer()-_editTime > 1000) 
			{	// this is the first time the user clicked on the item
				var attributes:Object = ItemAttributes(_selectedNode);
				if (attributes.type != "dir")
				{
					_editCell = event.rowIndex;
					_editTime = getTimer();
				}
				event.preventDefault();
				return;
			}
		}
		
		public function treeEditBegin(event:ListEvent) : void
		{
			if (event.rowIndex != _editCell) event.preventDefault();
		}
		
		public function treeEditEnd(event:ListEvent) : void
		{
			var tree:Tree = event.target as Tree;
			var attributes:Object = ItemAttributes(_selectedNode);
			var to:String = (tree.itemEditorInstance as TextInput).text;

			event.preventDefault();
			tree.destroyItemEditor();
			_editCell = -1;
			if (event.reason == ListEventReason.CANCELLED || attributes.id == to) return;
			
			var vars:URLVariables = new URLVariables();
			vars.action = "move_" + (attributes.isTemplate? "template" : "page");
			vars.path = (attributes.path || "") + "*";
			vars.elementId = attributes.id;
			vars.type = attributes.type;
			vars.userId = _username;
			vars.to = to;

			WebRequest.Post(_conn, LoadTreeEvent, vars);
		}

		public function AskPackagePortal():void
		{
			BuildForm(_packagingForm, _packaging);
			ShowPopup(_pnlPackagePortal, true);
		}
		
		private function BuildForm(form:Form, manifest:XMLList):void {
			form.removeAllChildren();
			form.data = {_values:[], values:function():Object{ var r:Object={}; for each (var i:* in this._values) { var o:Object = i(); for (var p:String in o) r[p] = o[p]; } return r; }};
			for each (var item:XML in manifest.children()) {
				switch(item.localName()) {
					case "select":		AddFormSelect(form, item.@name, item.option);					break;
					case "textarea":	AddFormTextArea(form, item.@name, item.@width, item.@height);	break;
					case "button":		AddFormButton(form, item.@name, item.@value);					break;
				}
			}
		}
		
		private function AddFormSelect(form:Form, label:String, options:XMLList):void {
			var select:ComboBox = new ComboBox();
			select.name = "_select" + label;
			select.labelFunction = BuildFormOptionLabel;
			select.dataProvider = options;
			AddFormItem(form, label, select);
			form.data[label+"Value"] = function():Object { return select.value; };
			form.data._values.push(function():Object { var o:Object = {}; o[label+"Name"] = select.value.@name.toString(); o[label+"Value"] = select.value.@value.toString(); return o; });
		}
		
		public function BuildFormOptionLabel(item:Object):String {
			var node:XML = XML(item);
			return item.@name + " (" + item.@value + ")";
		}
		
		private function AddFormTextArea(form:Form, label:String, width:*, height:*):void {
			var textarea:TextArea = new TextArea();
			textarea.name = "_textarea" + label;
			var w:int = width? parseInt(width) : NaN;
			var h:int = height? parseInt(height) : NaN;
			if (w) textarea.width = w;
			if (h) textarea.height = h;
			AddFormItem(form, label, textarea);
			form.data[label+"Value"] = function():String { return textarea.text; };
			form.data._values.push(function():Object { var o:Object = {}; o[label] = textarea.text; return o; });
		}
		
		private function AddFormItem(form:Form, label:String, input:DisplayObject):void {
			var item:FormItem = new FormItem();
			item.name = "_item" + label;
			item.label = label;
			item.addChild(input);			
			form.addChild(item);
		}
		
		private function AddFormButton(form:Form, label:String, action:String):void {
			var button:Button = new Button();
			button.name = "_button" + label;
			button.data = action;
			button.label = label;
			button.addEventListener(MouseEvent.CLICK, BuildFormButtonClicked);
	
			if (!form.data.hbox) { form.data.hbox = new HBox(); form.addChild(form.data.hbox); }
			form.data.hbox.addChild(button);
		}
		
		private function BuildFormButtonClicked(event:MouseEvent):void {
			var button:Button = event.target as Button;
			var action:String = button.data as String;
			var m:Object;
			if (/^http/.test(action)) navigateToURL(new URLRequest(action), "_blank");
			else if (executeFunction(action)) ; // do nothing
			else Alert.show("Error: Cannot perform '" + action + "'");
			
			function executeFunction(action:String):Boolean {
				var m:Object = /^\s*\{\s*([a-z0-9_]+)\s*\(\s*(.*?)\s*\)\s*\}\s*$/i.exec(action);
				if (!m) return false;
				var args:Array = [], a:Array = m[2]? m[2].split(/\s*,\s*/g) : [];
				for each (var arg:String in a) args.push(arg.replace(/^"|^'|'$|$"/g, ""));
				switch (m[1]) { // is there a way to do this generic (e.g. 'new Function(action.replace(/^\{|\}$/g,""))()')?
					case "PackagePortal": PackagePortal.apply(this, args); return true;
					default: return false;
				}
			}
		}

		public function PackagePortal(packagingTool:String, deployUrl:String):void
		{
			_packagingForm.data.deployUrl = deployUrl;
			
			var dict:Array = new Array();
			
			dict.push({name:"action", val:"package_repository"});
			dict.push({name:"userId", val:_username});
			dict.push({name:"packagingTool", val:packagingTool});
			var values:Object = _packagingForm.data.values();
			for (var v:String in values) dict.push({name:v, val:values[v]});

			WebRequest.Get(_conn, PackagingResult, dict);
			
			HidePopups();
		}
		
		private function PackagingResult(event:Event):void {
			ShowResult(event);
			if (!GetResult(event).isSuccess) return;
			
			var url:String = _packagingForm.data.deployUrl as String;
			url += (url.match(/\?/) ? "&" : "?")+"username="+_username+
					"&lang="+_packagingForm.data.LanguageValue().@name +
					"&site="+_packagingForm.data.LanguageValue().@value +
					"&ip="+_packagingForm.data.HostValue().@value +
					"&host="+_packagingForm.data.HostValue().@name +
					"&comments="+escape(_packagingForm.data.CommentsValue()) +
					"&fff="+UIDUtil.createUID() + ".txt";

			if (_packagingForm.data.HostValue().@name == "CMS") return;

			var jscmd:String = "window.open('"+url+"', 'newwindow', 'height=500, width=600, toolbar=no, scrollbars=yes'); void(0);";
			
			var request:URLRequest = new URLRequest("javascript:"+jscmd);
			request.method = URLRequestMethod.GET;
/*			
			var variables:URLVariables = new URLVariables();
			variables.username = _username;
			variables.lang = lang.value.@name;
			variables.site = lang.value.@value;
			variables.ip = host.value.@value;
			variables.host = host.value.@name;
			variables.comments = comments.text;
			variables.fff = mx.utils.UIDUtil.createUID() + ".txt";
			
			request.data = variables;
*/
			navigateToURL(request, "_self");
		}
				
		public function GeneratePortal():void 
		{
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"generate_repository"});
			dict.push({name:"userId", val:_username});

			_txtStatus.text = "Initiating portal generation...";
			
			if (_statusTimer != null) _statusTimer.stop();
			_statusTimer = new Timer(2000);
			_statusTimer.addEventListener(TimerEvent.TIMER, GeneratePortalStatus);
			_statusTimer.start();

			WebRequest.Get(_conn, GeneratePortalResult, dict);
		}
		
		private function GeneratePortalResult(event:Event) : void
		{
			var result:Object = GetResult(event);
			if (result.isSuccess) _txtStatus.text = result.text;
			else if (/like to terminate/.test(result.text))
			{
				Alert.show(result.text, "Portal generation in process", Alert.YES | Alert.NO, null, KillGeneratePortal, null, Alert.NO);
				return;
			} 
			else if (/warnings/.test(result.text))
			{
				_txtStatus.data = result.text.toString();
				_txtStatus.htmlText = result.text.toString().split(/[\n\r]/, 2)[0] + " <i>(Click here to see more...)</i>";
				_txtStatus.addEventListener(MouseEvent.CLICK, ShowStatusLink);
				callLater(_txtStatus.addEventListener, [FlexEvent.VALUE_COMMIT, RemoveStatusLink]);
			} else Alert.show(result.text);
		}
		
		private function ShowStatusLink(e:Event):void
		{
			Alert.show(_txtStatus.data as String);
		}
		
		private function RemoveStatusLink(e:Event):void
		{
			_txtStatus.removeEventListener(MouseEvent.CLICK, ShowStatusLink);
			_txtStatus.removeEventListener(FlexEvent.VALUE_COMMIT, RemoveStatusLink);
			_txtStatus.data = null;
		}
		
		private function KillGeneratePortal(event:CloseEvent) : void
		{
			if (event.detail != Alert.YES) return;
			
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"generate_repository_kill"});
			dict.push({name:"userId", val:_username});

			_txtStatus.text = "Trying to terminate portal generation...";
			
			WebRequest.Get(_conn, ShowResult, dict);
		}
		
		private function GeneratePortalStatus(event:TimerEvent) : void
		{
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"generate_status"});
			dict.push({name:"userId", val:_username});

			WebRequest.Get(_conn, GeneratePortalStatusCallback, dict);
		}
		
		private function GeneratePortalStatusCallback(event:Event) : void
		{
			var result:Object = GetResult(event);
			if (result.isSuccess) _txtStatus.text = result.text;
			else if (/initiated/.test(_txtStatus.text)) _txtStatus.text = "";
			if (_statusTimer && (!result.isSuccess || /completed/.test(result.text)))
			{
				_statusTimer.stop();
				_statusTimer.removeEventListener(TimerEvent.TIMER, GeneratePortalStatus);
				_statusTimer = null;
			}
		}
		
		public function GeneratePage():void 
		{
			_selectedNode = _treeCMS.selectedItem as XML;

			var attr:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.parent().@id);

			var dict:Array = new Array();
							
			dict.push({name:"action", val:"generate_page"});
			dict.push({name:"path", val:parent.GetFullPath() + "*"});
			dict.push({name:"pageId", val:attr.GetName()});
			dict.push({name:"userId", val:_username});

			WebRequest.Get(_conn, ShowResult,dict);
		}
		
		public function AskDeleteTemplate():void { Alert.show("Are you sure you wish to delete this template?", "Delete template", Alert.OK | Alert.CANCEL, null, DeleteTemplate, null, Alert.CANCEL);}
		public function DeleteTemplate(evt:Object):void
		{
			if (evt.detail != Alert.OK)
				return;

//			Alert.show("template deletion not supported yet!");

			_selectedNode = _treeCMS.selectedItem as XML;

			var attr:CMSAttributes = new CMSAttributes(_selectedNode.@id);
			var parent:CMSAttributes = new CMSAttributes(_selectedNode.parent().@id);

			var dict:Array = new Array();
							
			dict.push({name:"action", val:"delete_template"});
			dict.push({name:"path", val:parent.GetFullPath() + "*"});
			dict.push({name:"elementId", val:attr.GetName()});
			dict.push({name:"type", val:parent.GetType()});
			dict.push({name:"userId", val:_username});
						
			WebRequest.Get(_conn, LoadTreeEvent,dict);
		}

		public function SaveSectionStates(form:Form) : void
		{
			var vars:URLVariables = new URLVariables();
			vars.action = "set_section_states";
			vars.path = _displayedNode.path;
			vars.pageId = _displayedNode.id;
			vars.userId = _username;
			vars.states = GetSectionStates(form);
							
			WebRequest.Post(_conn, function():void{}, vars);
		}
		
		private function GetSectionStates(form:Form) : String
		{
			var children:Array = form.getChildren();
			for (var states:Array = [], i:int = 0; i != children.length; ++i) {
				var box:UIComponent = children[i] as UIComponent;
				if (box.styleName != "section") continue;
				var c:Image = (box.getChildAt(0) as Container).getChildAt(0) as Image;
				if (!c) continue;
				var name:String = ((box.getChildAt(0) as Container).getChildAt(1) as Text).text;
				states.push('<section name="'+name+'" state="'+((c.data as Boolean)?2:1)+'"/>');
			}
			return '<states>'+states.join('')+'</states>';
		}
		
		public function SavePage(callback:Function = null) : void
		{
			var children:Array = _frmPage.getChildren();

			var content:String = "<root>";

			var templateId:String = ((_frmPage.getChildByName("templateField") as FormItem).getChildAt(0) as TextInput).text.replace(/\//g, "*").replace(/^\*/, "");
			
			for each (var c:Container in children) 
				for each(var o:Object in c.getChildren())
				{
					var fi:FormItem = o as FormItem;
					if (!fi) continue;
					
					var ta:TextArea = fi.getChildAt(0) as TextArea;
					if (!ta) continue;
									
					var escaped:String = EscapeXML(ta.text);
					content += "<param name=\"" + fi.label + "\" section=\"" + fi.data.toString() + "\"><![CDATA[" + escaped + "]]></param>";
				}

			for each (c in children) 
				for each(o in c.getChildren())
				{
					fi = o as FormItem;
					if (!fi) continue;
					
					var combo:ComboBox = fi.getChildAt(0) as ComboBox;
					if (!combo) continue;
									
					escaped = EscapeXML(combo.text);
					content += "<param name=\"" + fi.label + "\" section=\"" + fi.data.toString() + "\"><![CDATA[" + escaped + "]]></param>";
				}
			
			content += "</root>";
			
			var vars:URLVariables = new URLVariables();
			vars.action = "set_page";
			vars.path = _displayedNode.path;
			vars.templateId = templateId;
			vars.pageId = _displayedNode.id;
			vars.userId = _username;
			vars.title = _displayedNode.title;
			vars.comment = _displayedNode.comment;
			vars.content = content;
			vars.states = GetSectionStates(_frmPage);
							
			WebRequest.Post(_conn, callback || ShowSaveResult, vars);
		}
		
		private function EscapeXML(text:String):String 
		{
			return text.replace(/&/g,"&amp;").replace(/\</g,"&lt;").replace(/\>/g,"&gt;");//.replace(/\r(?!\n)/g,"\r\n");
		}
		
		public function SaveTemplate(callback:Function = null):void
		{
			var vars:URLVariables = new URLVariables();
			vars.action = "set_template";
			vars.path = _displayedNode.path;
			vars.templateId = _displayedNode.id;
			vars.userId = _username;
			vars.title = _displayedNode.title;
			vars.comment = _displayedNode.comment;
			vars.content = _txtContent.text;
							
			WebRequest.Post(_conn, callback || ShowSaveResult, vars);
		}
		
		public function DoLogin():void
		{
			var dict:Array = new Array();
							
			dict.push({name:"action", val:"do_login"});
			dict.push({name:"userId", val:_txtUsername.text});
			dict.push({name:"password", val:_txtPassword.text});

			WebRequest.Get(_conn, VerifyLogin, dict, VerifyLogin);
		}
		
		public function VerifyLogin(evt:Event):void
		{
			if (evt.type == IOErrorEvent.IO_ERROR || evt.type == SecurityErrorEvent.SECURITY_ERROR) 
			{
				alert("Could not log in due to the following error:\n\n"+(evt as ErrorEvent).text+"\n\nCould it be that you have the wrong conn or repo parameters in the FlashVars?");
				return;
			}
			
			var response:String = evt.target.data as String;

			var result:XMLList = new XMLList(response);
						
			if( result.attribute("status").toLowerCase() != "success")
			{
				Alert.show(result.child(0));
				return;
			}
			else
			{
				_username = _txtUsername.text;
				this.currentState = "welcome";
				_treeCMS.enabled = true;
				_cnvForms.enabled = true;
				_divider.enabled = true;
				
				_packaging = result.packaging;
				LoadTree();
			}
		}
		
		private function UpdateVersions(xml:XML, type:String) : Object
		{
			var selected:int = -1, nodeDetails:Object = null;
			var versions:ArrayCollection = new ArrayCollection();
			for (var i:int = 0; i < xml.version.length(); ++i)
			{
				var n:XML = xml.version[i];
				var id:String = String(n.@Id);
				if (!(/[\/\\\*]/.test(id))) id = "\\" + id;
				var j:int = /[\/\\\*][^\/\\\*]+$/.exec(id).index;
				versions.addItem({label:"#"+n.@Revision+": "+n.@Date+" by "+n.@Author, id:id.substr(j+1), path:id.substr(0,j+1).replace(/[\/\\]/g,"*"), title:''+n.@Title, comment:''+n.@Comment, type:type});
				if (!(/_history/.test(id))) nodeDetails = versions[versions.length-1];
				if (xml.@current == n.@Id) selected = i;
			}
			_versions.dataProvider = versions;
			_versions.data = {current:selected};
			_versions.selectedIndex = selected;
			_currentItem.text = nodeDetails.type + ": " + (nodeDetails.path + nodeDetails.id).replace(/\*/g, "/");
			return nodeDetails;
		}
				
		public function GoToVersion(combo:ComboBox) : void
		{
			if (HandleDirty(SaveChangesBeforeSwitchingVersions, combo)) return;
			
			_txtStatus.text = "";
			
			var selected:Object = combo.selectedItem;

			var dict:Array = new Array();
			if (_displayedNode.type == "page")
			{
				dict.push({name:"action", val:"get_page"});
				dict.push({name:"userId", val:_username});
				dict.push({name:"pageId", val:selected.id});
				dict.push({name:"path", val:selected.path});
				WebRequest.Get(_conn, ShowPage, dict);
			}
			else
			{
				dict.push({name:"action", val:"get_template"});
				dict.push({name:"userId", val:_username});
				dict.push({name:"templateId", val:selected.id});
				dict.push({name:"path", val:selected.path});
				WebRequest.Get(_conn, ShowTemplate, dict);
			}
		}

		public function SaveChangesBeforeSwitchingVersions(event:CloseEvent) : void 
		{
			var combo:ComboBox = (event.target as Alert).data as ComboBox;
			if (event.detail == Alert.NO) 
			{
				SetDirty(false);
				GoToVersion(combo);
			}
			else if (event.detail == Alert.CANCEL) combo.selectedIndex = combo.data.current;
			else SavePage(SwitchToSelectedVersion);
		}
		
		private function SwitchToSelectedVersion(event:Event) : void
		{
			ShowSaveResult(event);
			if (GetResult(event).isSuccess) GoToVersion(_versions);
			else _versions.selectedIndex = _versions.data.current;
		}
		
		private function HandleDirty(callback:Function, data:Object = null) : Boolean
		{
			if (_isDirty) {
				Alert.show("Do you want to save the changes you made?", "Unsaved Changes", Alert.YES | Alert.NO | Alert.CANCEL, null, callback, null, Alert.YES).data = data;
				return true;
			} else return false;
		}
	}
}
